package cz.possoft.client.saveload.jaxb;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import cz.possoft.client.saveload.SaveLoadException;
import cz.possoft.client.saveload.SaveLoadExecutor;
import cz.possoft.client.utils.GameInvocationUtils;
import cz.possoft.converter.jaxb.JAXBGameConverter;
import cz.possoft.serializable.generic.Game;

/**
 * Executor is based on serializing the game to XML before closing and writing this XML to a file. Same approach for loading.
 * So games are saved in "XML" format.
 * 
 * @author mposolda
 */
public class JAXBSaveLoadExecutor implements SaveLoadExecutor {

	public Game loadGame(String fileName) throws SaveLoadException {
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(fileName));
			// name of the game class should be saved in file on first line of the file.
			String gameKey = reader.readLine().trim();
			return getGameFromXMLReaderAndKey(reader, gameKey);
		}
		catch (Exception e) {
			throw new SaveLoadException("Error in loading game from file " + fileName + ".", e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException exception) {
                    exception.printStackTrace();
                }
            }
        }
	}

	public <E extends Game> void saveGame(E game, String fileName) throws SaveLoadException {
		PrintWriter writer = null;
		try {
			writer = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
			// write className to the file first.
			String prefixx = game.getGameKey();
			writer.println(prefixx);
			writeXMLGameToWriter(game, writer);
		}
		catch (Exception e) {
			throw new SaveLoadException("Error in saving game " + game + " to file " + fileName + ".", e);
        } finally {
            if (writer != null) {
                    writer.close();
            }
        }
	}
	
	/**
	 * Source should be String in simple format "key|xml". For example "Chess|<?xml ......>" . 
	 * 
	 * So implementation is based on getting game key and getting XML and then create Game from gameKey and XML content.
	 */
	public Game getGameFromServer(Serializable source) throws SaveLoadException {
		StringReader stringReader = null;
		try {
			String sourceString2 = (String)source;
			String sourceString = sourceString2.substring("LOAD_GAME_HERE: ".length());
			int pipeIndex = sourceString.indexOf('|');
			String gameKey = sourceString.substring(0, pipeIndex);
			String xmlString = sourceString.substring(pipeIndex + 1);
			stringReader = new StringReader(xmlString);
			return getGameFromXMLReaderAndKey(stringReader, gameKey);
		}
		catch (Exception e) {
			throw new SaveLoadException("Error in loading game from server. Game source is " + source + " .", e);
		}
		finally {
			stringReader.close();
		}
	}

	/**
	 * Get String with game key and XML. Format is "key|XML".
	 */
	public Serializable getObjectForSendingToServer(Game sourceGame) throws SaveLoadException {
		StringWriter strWriter = null;
		try {
			strWriter = new StringWriter();
			// write className to the file first.
			String prefixx = sourceGame.getGameKey();
			strWriter.write("LOAD_GAME_HERE: " + prefixx + "|");
			writeXMLGameToWriter(sourceGame, strWriter);
			return strWriter.getBuffer().toString();
		}
		catch (Exception e) {
			throw new SaveLoadException("Error in saving game " + sourceGame + " to XML String.", e);
		}
		finally {
			try {
				strWriter.close();
			}
			catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
	}	
	
	/**
	 * Get instance of game when reader can be any reader where XML can be read from. And key is key of the game.
	 * Warning: Reader is not closed after finishing and should be closed from enclosing method.
	 * 
	 * @param readerWithXMLString
	 * @param gameKey
	 * @return
	 */
	private Game getGameFromXMLReaderAndKey(Reader readerWithXML, String gameKey) throws JAXBException {
		Game helperGame = GameInvocationUtils.getInstanceOfGame(gameKey);
		// now get instance of converter and convert game from xml.
		JAXBGameConverter<?> jaxbConverter = helperGame.getJAXBGameConverter();
		JAXBContext context = jaxbConverter.getJAXBContext();
		Unmarshaller unmarshaller = context.createUnmarshaller();
		JAXBElement<?> element = (JAXBElement<?>)unmarshaller.unmarshal(readerWithXML);
		return jaxbConverter.convertJAXBElementToGame(element);			
	}
	
	/**
	 * Method is used to serialize XML content and write it into writer.
	 * 
	 * @param game
	 * @param writer
	 */
	private <E extends Game> void writeXMLGameToWriter(E game, Writer writer) throws JAXBException {
		JAXBGameConverter<E> jaxbConverter = (JAXBGameConverter<E>)game.getJAXBGameConverter();			
		JAXBContext context = jaxbConverter.getJAXBContext();
		Marshaller marshaller = context.createMarshaller();
		JAXBElement<?> element = jaxbConverter.convertGameToJAXBElement(game);
		marshaller.marshal(element, writer);		
	}

}
